package org.papervision3d.view 
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	
	import org.papervision3d.core.culling.DefaultLineCuller;
	import org.papervision3d.core.culling.DefaultParticleCuller;
	import org.papervision3d.core.culling.DefaultTriangleCuller;
	import org.papervision3d.core.culling.ILineCuller;
	import org.papervision3d.core.culling.IParticleCuller;
	import org.papervision3d.core.culling.ITriangleCuller;
	import org.papervision3d.core.culling.RectangleLineCuller;
	import org.papervision3d.core.culling.RectangleParticleCuller;
	import org.papervision3d.core.culling.RectangleTriangleCuller;
	import org.papervision3d.core.culling.ViewportObjectFilter;
	import org.papervision3d.core.geom.renderables.Triangle3D;
	import org.papervision3d.core.log.PaperLogger;
	import org.papervision3d.core.render.IRenderEngine;
	import org.papervision3d.core.render.command.IRenderListItem;
	import org.papervision3d.core.render.command.RenderableListItem;
	import org.papervision3d.core.render.data.RenderHitData;
	import org.papervision3d.core.render.data.RenderSessionData;
	import org.papervision3d.core.utils.InteractiveSceneManager;
	import org.papervision3d.core.view.IViewport3D;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.view.layer.ViewportBaseLayer;
	import org.papervision3d.view.layer.ViewportLayer;

	/**
	 * @Author Ralph Hauwert
	 */
	 
	/* Changed to protected methods on 11/27/2007 by John */
	/* Added LineCulling on 22 May 08 by Seb Lee-Delisle */
	public class Viewport3D extends Sprite implements IViewport3D
	{
		//use namespace org.papervision3d.core.ns.pv3dview;
		/** @private */
		protected var _width:Number;
		/** @private */
		protected var _hWidth:Number;
		/** @private */
		protected var _height:Number;
		/** @private */
		protected var _hHeight:Number;
		/** @private */
		protected var _autoClipping:Boolean;
		/** @private */
		protected var _autoCulling:Boolean;
		/** @private */
		protected var _autoScaleToStage:Boolean;
		/** @private */
		protected var _interactive:Boolean;
		/** @private */
		protected var _lastRenderer:IRenderEngine;
		/** @private */
		protected var _viewportObjectFilter:ViewportObjectFilter;
		/** @private */
		protected var _containerSprite:ViewportBaseLayer;
		/** @private */
		protected var _layerInstances:Dictionary;
		/**
		 * <span lang="ja">Viewport3Dsprite の幅と高さを取得します。</span>
		 * <span lang="en">sizeRectangle stores the width and the height of the Viewport3D sprite</span>
		 * @see #viewportWidth
		 * @see #viewportHeight
		 */
		public var sizeRectangle:Rectangle;
		/**
		 * <span lang="ja">cullingRectangle はカリングした矩形を取得します。triangleを描画する範囲を確定する用途に使用します。</span>
		 * <span lang="en">cullingRectangle stores the width, height, x, y of the culling rectangle. It's used to determine the bounds in which the triangles are drawn.</span>
		 * @see #autoCulling
		 */
		public var cullingRectangle:Rectangle;
		/**
		 * <span lang="ja">triangle を BasicRenderEngine でレンダリングされないようにする為に cullingRectangle を使用します。</span>
		 * <span lang="en">triangleCuller uses the cullingRectangle to determine which triangles will not be rendered in BasicRenderEngine</span>
		 * @see #autoCulling
		 */
		public var triangleCuller:ITriangleCuller;
		/**
		 * <span lang="ja">パーティクルが BasicRenderEngine でレンダリングされないようにする為に cullingRectangle を使用します。</span>
		 * <span lang="en">particleCuller uses the cullingRectangle to determine which particles will not be rendered in BasicRenderEngine</span>
		 * @see #autoCulling
		 */
		public var particleCuller:IParticleCuller;
		/**
		 * <span lang="ja">lineCuller はパーティクル をBasicRenderEngine でレンダリングされないようにする為に cullingRectangle を使用します。</span>
		 * <span lang="en">lineCuller uses the culling Rectangle to determine which particles will not be rendered in BasicRenderEngine</span>
		 * @see #autoCulling
		 */
		public var lineCuller:ILineCuller;
		/**
		 * <span lang="ja">最後のレンダリング処理を行った RenderableListItem(Triangles,Lines,Pixels,Particles,Fog) を取得します。これは hitTestPoint2D の hitTests を行う際に利用します。</span>
		 * <span lang="en">lastRenderList stores RenderableListItems (Triangles, Lines, Pixels, Particles, Fog) of everything that was rendered in the last pass. This list is used to determine hitTests in hitTestPoint2D.</span>
		 * @see #hitTestPoint2D()
		 */
		public var lastRenderList:Array;
		/**
		 * <span lang="ja">ユーザーのマウス操作などのインタラクションを制御する InteractiveSceneManager インスタンスを取得します。</span>
		 * <span lang="en">interactiveSceneManager manages the interaction between the user's mouse and the Papervision3D scene. 
		 * This is done by checking the mouse against renderHitData. renderHitData is generated from hitTestPoint2D and passed into the interactiveSceneManager to check agains the various mouse actions.</span>
		 * @see #hitTestPoint2D()
		 * @see org.papervision3d.core.utils.InteractiveSceneManager#renderHitData
		 */
		public var interactiveSceneManager:InteractiveSceneManager;
		/** @private */
		protected var renderHitData:RenderHitData;
		private var stageScaleModeSet :Boolean = false; 
		
		/**
		 * <span lang="ja">新しい Viewport3D インスタンスを作成します。</span>
		 * @param viewportWidth		ビューポートの幅です。
		 * @param viewportHeight	ビューポートの高さです。
		 * @param scaleToStage		ビューポートをステージに合わせるかどうかのフラグです。
		 * @param interactive		シーンがインタラクティブかどうかの設定です。3Dオブジェクトにマウスイベントを登録する場合は、trueを設定します。
		 * @param autoClipping 		クリッピングを有効にするかどうかを設定します。
		 * @param autoCulling 		カリングを有効にするかどうかを設定します。
		 */
		public function Viewport3D(viewportWidth:Number = 640, viewportHeight:Number = 480, autoScaleToStage:Boolean = false, interactive:Boolean = false, autoClipping:Boolean = true, autoCulling:Boolean = true)
		{
			super();
			init();
			
			this.interactive = interactive;
			
			this.viewportWidth = viewportWidth;
			this.viewportHeight = viewportHeight;
			
			this.autoClipping = autoClipping;
			this.autoCulling = autoCulling;
			
			this.autoScaleToStage = autoScaleToStage;
			
			this._layerInstances = new Dictionary(true);
		}

		/**
		 * <span lang="ja">クラス内の全ての参照を削除し、ガベージコレクションの対象となるようにします。</span>
		 * <span lang="en">Removes all references and sets the viewport's
		 * InteractiveSceneManager to null for a future
		 * garbage collection sweep</span>
		 */
		public function destroy():void
		{
			if(interactiveSceneManager)
			{
				interactiveSceneManager.destroy();
				interactiveSceneManager = null;
			}
			lastRenderList = null;
		}

		/**
		 * @private
		 */
		protected function init():void
		{
			this.renderHitData = new RenderHitData();
			
			lastRenderList = new Array();
			sizeRectangle = new Rectangle();
			cullingRectangle = new Rectangle();
			
			_containerSprite = new ViewportBaseLayer(this);
			_containerSprite.doubleClickEnabled = true;
			
			addChild(_containerSprite);
		
			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
		}

		/**
		 * <span lang="ja">RenderHitData に備えてマウスの x と y の値をチェックします。</span>
		 * <span lang="en">Checks the Mouse x and y against the <code>RenderHitData</code></span>
		 * @return RenderHitData of the current mouse location
		 * @see RenderHitData
		 */		
		public function hitTestMouse():RenderHitData
		{
			var p:Point = new Point(containerSprite.mouseX, containerSprite.mouseY);
			return hitTestPoint2D(p);
		}

		/**
		 * <span lang="ja">ビューポートの RenderHitData に備えてポイントをチェックします。</span>
		 * <span lang="en">Checks a <code>Point</code> against the <code>RenderHitData</code>
		 * of the viewport</span>
		 * @param point	 a 2d <code>Point</code> you want to analyze into 3d space
		 * @return <code>RenderHitData</code> of the given <code>Point</code>
		 */
		public function hitTestPoint2D(point:Point):RenderHitData
		{
			renderHitData.clear();
			if(interactive)
			{
				var rli:RenderableListItem;
				var rhd:RenderHitData = renderHitData;
				var rc:IRenderListItem;
				for(var i:uint = lastRenderList.length;rc = lastRenderList[--i]; )
				{
					if(rc is RenderableListItem)
					{
						rli = rc as RenderableListItem;
						rhd = rli.hitTestPoint2D(point, rhd);
						if(rhd.hasHit)
						{				
							return rhd;
						}
					}
				}
			}
			return renderHitData;
		}
		
		public function hitTestPointObject(point:Point, object:DisplayObject3D):RenderHitData
		{
			if(interactive){
				var rli:RenderableListItem;
				var rhd:RenderHitData = new RenderHitData();
				var rc:IRenderListItem;
				
				for(var i:uint = lastRenderList.length; rc = lastRenderList[--i]; )
				{
					if(rc is RenderableListItem)
					{
						rli = rc as RenderableListItem;
						
						if(rli.renderableInstance is Triangle3D){
							if(Triangle3D(rli.renderableInstance).instance != object)
								continue;
						}else{
							continue;
						}
						
						rhd = rli.hitTestPoint2D(point, rhd);
						
						if(rhd.hasHit)
						{				
							return rhd;
						}
					}
				}
			}
			
			return new RenderHitData();
		}

		/**
		 * 与えられたDisplayObject3DのViewportLayerをつくる、または受け取る
		 * Creates or receives a <code>ViewportLayer</code> of the given <code>DisplayObject3D</code>
		 * @param do3d			A <code>DisplayObject3D</code> used to either find the layer or create a new one
		 * @param createNew		Forces the creation of a new layer
		 * @param recurse		Adds the <code>DisplayObject3D</code> as well as all of its children to a new layer
		 * @return <code>ViewportLayer</code> of the given <code>DisplayObject3D</code>
		 */
		public function getChildLayer(do3d:DisplayObject3D, createNew:Boolean = true, recurse:Boolean = true):ViewportLayer
		{
			return containerSprite.getChildLayer(do3d, createNew, recurse);
		}

		/**
		 * RenderListItemのレイヤーを取得する。ほとんどが内部使用
		 * Gets the layer of the RenderListItem. Most-likely internal use.
		 * @param rc			A RenderableListItem to look for
		 * @param setInstance	sets the container to the layer
		 * @return 				The found <code>ViewportLayer</code>
		 */
		public function accessLayerFor(rc:RenderableListItem, setInstance:Boolean = false):ViewportLayer
		{
			var do3d:DisplayObject3D;
			
			if(rc.renderableInstance)
			{
				do3d = rc.renderableInstance.instance;

				do3d = do3d.parentContainer ? do3d.parentContainer : do3d;
				
				if(containerSprite.layers[do3d])
				{
					if(setInstance)
					{
						do3d.container = containerSprite.layers[do3d];
					}
					return containerSprite.layers[do3d];
				}else if(do3d.useOwnContainer)
				{
					return containerSprite.getChildLayer(do3d, true, true);	
				}
			}
			
			return containerSprite;
		}

		/**
		 * ステージをリサイズする検出を始める為に、トリガーをステージに加える
		 * Triggered when added to the stage to start listening to stage resizing
		 */
		protected function onAddedToStage(event:Event):void
		{
			if(_autoScaleToStage) 
			{
				setStageScaleMode();
			}
			stage.addEventListener(Event.RESIZE, onStageResize);
			onStageResize();
		}

		/**
		 * ステージをリサイズするリスナーをステージから削除する為のトリガー
		 * Triggered when removed from the stage to remove the stage resizing listener
		 */
		protected function onRemovedFromStage(event:Event):void
		{
			stage.removeEventListener(Event.RESIZE, onStageResize);
		}
		
		/**
		 * ステージがリサイズされた時、ビューポートをリサイズする（autoScaleToStage == true の場合）
		 * Resizes the viewport when the stage is resized (if autoScaleToStage == true)
		 */
		protected function onStageResize(event:Event = null):void
		{
			if(_autoScaleToStage)
			{
				viewportWidth = stage.stageWidth;
				viewportHeight = stage.stageHeight;
			}
		}

		protected function setStageScaleMode() : void
		{
			if(!stageScaleModeSet)
			{
				PaperLogger.info("Viewport autoScaleToStage : Papervision has changed the Stage scale mode."); 
			
            	stage.align = StageAlign.TOP_LEFT;
            	stage.scaleMode = StageScaleMode.NO_SCALE;	
            	stageScaleModeSet = true; 		
			}
			
		}

		/**
		 * <span lang="ja">Viewport3D の幅を取得または設定します。</span>
		 * <span lang="en">Sets the viewport width</span>
		 */
		
		public function get viewportWidth():Number { return _width; }
		public function set viewportWidth(width:Number):void
		{
			_width = width;
			_hWidth = width / 2;
			containerSprite.x = _hWidth;
			
			cullingRectangle.x = -_hWidth;
			cullingRectangle.width = width;
			
			sizeRectangle.width = width;
			if(_autoClipping)
			{
				scrollRect = sizeRectangle;
			}
		}

		/**
		 * <span lang="ja">Viewport3D の高さを取得または設定します。</span>
		 * <span lang="en">Sets the the height of the <code>Viewport3D</code></span>
		 */
		public function get viewportHeight():Number { return _height; }
		public function set viewportHeight(height:Number):void
		{
			_height = height;
			_hHeight = height / 2;
			containerSprite.y = _hHeight;
			
			cullingRectangle.y = -_hHeight;
			cullingRectangle.height = height;
			
			sizeRectangle.height = height;
			if(_autoClipping)
			{
				scrollRect = sizeRectangle;
			}
		}

		

		/**
		 * <span lang="ja">Viewport3D に格納されたコンテナーを取得します。</span>
		 * <span lang="en">The <code>Sprite</code> holding the <code>Viewport3D</code></span>
		 */
		public function get containerSprite():ViewportLayer
		{
			return _containerSprite;	
		}
		
		/**
		 * <span lang="ja">クリッピングが使用可能であるかどうかを取得または設定します。( Sprite.scrollRect を使うことでビューポートの外側の矩形にあるビットマップデータをレンダリングしません)</span>
		 * <span lang="ja">Whether clipping is enabled (not rendering bitmap data outside the rectangle of the viewport by making use of the <code>Sprite.scrollRect</code>)</span>
		 * @see flash.display.Sprite#scrollRect
		 * @see http://www.gskinner.com/blog/archives/2006/11/understanding_d.html
		 */
		public function get autoClipping():Boolean
		{
			return _autoClipping;	
		}

		public function set autoClipping(clip:Boolean):void
		{
			if(clip)
			{
				scrollRect = sizeRectangle;
			}else
			{
				scrollRect = null;
			}
			_autoClipping = clip;
		}
		
		/**
		 * <span lang="ja">カリングが使用可能であるかどうかを取得または設定します。(他のトライアングルの裏面に隠れたトライアングルをレンダリングしない）</span>
		 * <span lang="en">Whether culling is enabled (not rendering triangles hidden behind other triangles)</span>
		 * @see #lineCuller
		 * @see #particleCuller
		 * @see #triangleCuller
		 */
		public function get autoCulling():Boolean
		{
			return _autoCulling;
		}
		
		public function set autoCulling(culling:Boolean):void
		{
			if(culling)
			{
				triangleCuller = new RectangleTriangleCuller(cullingRectangle);
				particleCuller = new RectangleParticleCuller(cullingRectangle);
				lineCuller = new RectangleLineCuller(cullingRectangle);
			}else if(!culling)
			{
				triangleCuller = new DefaultTriangleCuller();
				particleCuller = new DefaultParticleCuller();
				lineCuller = new DefaultLineCuller();
			}
			_autoCulling = culling;	
		}

		/**
		 * <span lang="ja">ステージのサイズに自動的にフィットさせるかどうかを取得または設定します。</span>
		 * <span lang="en">Whether the <code>Viewport3D</code> should scale with the <code>Stage</code></span>
		 */
		public function get autoScaleToStage():Boolean { return _autoScaleToStage; }
		public function set autoScaleToStage(scale:Boolean):void
		{
			_autoScaleToStage = scale;
			if(scale && stage != null)
			{
				setStageScaleMode();
				onStageResize();
			}
			
		}
		
		
		/**
		 * <span lang="ja">マウスイベントを有効にするかどうかを取得または設定します。</span>
		 * <span lang="en">Whether the <code>Viewport3D</code> should listen for <code>Mouse</code> events and create an <code>InteractiveSceneManager</code></span>
		 */
		public function get interactive():Boolean { return _interactive; }
		public function set interactive(b:Boolean):void
		{
			if(b != _interactive)
			{
				if(_interactive && interactiveSceneManager)
				{
					interactiveSceneManager.destroy();
					interactiveSceneManager = null;
				}
				_interactive = b;
				if(b)
				{
					interactiveSceneManager = new InteractiveSceneManager(this);
				}
			}
		}
		

		/**
		 * <span lang="ja">ViewportLayerをレンダリングされている3Dデータより優先させて2Dシーンにアップデートします。</span>
		 * <span lang="en">Updates a <code>ViewportLayer</code> prior to the 3d data being rendered into the 2d scene</span>
		 * @param renderSessionData		All the information regarding the current renderSession packed into one class
		 */
		public function updateBeforeRender(renderSessionData:RenderSessionData):void
		{
			lastRenderList.length = 0;
			
			if(renderSessionData.renderLayers)
			{
				for each(var vpl:ViewportLayer in renderSessionData.renderLayers)
				{ 
					vpl.updateBeforeRender();
				}
			}else
			{
				_containerSprite.updateBeforeRender();
			}
			
			_layerInstances = new Dictionary(true);
		}

		/**
		 * <span lang="ja">ViewportLayerを２Dシーンにレンダリングされた3Dデータの後にアップデートする</span>
		 * <span lang="en">Updates a <code>ViewportLayer</code> after the 3d data is rendered into the 2d scene</span>
		 * @param renderSessionData		All the information regarding the current renderSession packed into one class	
		 */
		public function updateAfterRender(renderSessionData:RenderSessionData):void
		{
			if(interactive)
			{
				interactiveSceneManager.updateAfterRender();
			}
			
			if(renderSessionData.renderLayers)
			{
				for each(var vpl:ViewportLayer in renderSessionData.renderLayers) 
				{
					vpl.updateInfo();
					vpl.sortChildLayers();
					vpl.updateAfterRender();
				}
			}else
			{
				containerSprite.updateInfo();
				containerSprite.updateAfterRender();
			}
			
			containerSprite.sortChildLayers();
		}

		/**
		 * <span lang="ja">Viewport3D の ViewportObjectFilter 取得または設定します。</span>
		 * <span lang="en">Sets the <code>ViewportObjectFilter</code> of the <code>Viewport3D</code></span>
		 * @param vof		The <code>ViewportObjectFilter</code> you want applied
		 */
		public function get viewportObjectFilter():ViewportObjectFilter { return _viewportObjectFilter; }
		public function set viewportObjectFilter(vof:ViewportObjectFilter):void
		{
			_viewportObjectFilter = vof;
		}
		
	}
}
